#include "Line.h"

// dependency includes
#include "Circ.h"
#include "Rect.h"

Line::Line():
Shape(),
dir(0),
dist(0),
normal(0),
alterNormal(0) {

}

Line::Line(Transform* const position, const Vector2D& direction):
Line(position, 0.f, 0.f, direction.x, direction.y, -1.f) {

}

Line::Line(Transform* const position, const Vector2D& posOffset, const Vector2D& direction):
Line(position, posOffset.x, posOffset.y, direction.x, direction.y, -1.f) {

}

Line::Line(Transform* const position, float xOffset, float yOffset, const Vector2D& direction):
Line(position, xOffset, yOffset, direction.x, direction.y, -1.f) {

}

Line::Line(Transform* const position, const Vector2D& posOffset, float xDirection, float yDirection):
Line(position, posOffset.x, posOffset.y, xDirection, yDirection, -1.f) {

}

Line::Line(Transform* const position, float xOffset, float yOffset, float xDirection, float yDirection):
Line(position, xOffset, yOffset, xDirection, yDirection, -1.f) {

}

Line::Line(Transform* const position, const Vector2D& direction, float distance):
Line(position, 0.f, 0.f, direction.x, direction.y, distance) {

}

Line::Line(Transform* const position, const Vector2D& posOffset, const Vector2D& direction, float distance):
Line(position, posOffset.x, posOffset.y, direction.x, direction.y, distance) {

}

Line::Line(Transform* const position, float xOffset, float yOffset, const Vector2D& direction, float distance):
Line(position, xOffset, yOffset, direction.x, direction.y, distance) {

}

Line::Line(Transform* const position, const Vector2D& posOffset, float xDirection, float yDirection, float distance):
Line(position, posOffset.x, posOffset.y, xDirection, yDirection, distance) {

}

Line::Line(Transform* const position, float xOffset, float yOffset, float xDirection, float yDirection, float distance):
Shape(position, xOffset, yOffset),
dir(0),
dist(distance),
normal(0),
alterNormal(0) {
	dir = new Vector2D(xDirection, yDirection);
	dir->Normalize();
	CalculateNormals();
}

Line::~Line() {
	CleanUpDir();
	CleanUpNormal();
	CleanUpAlterNormal();
}

void Line::CleanUpDir() {
	if (dir != 0) {
		delete dir;
		dir = 0;
	}

	return;
}

void Line::CleanUpNormal() {
	if (normal != 0) {
		delete normal;
		normal = 0;
	}

	return;
}

void Line::CleanUpAlterNormal() {
	if (alterNormal != 0) {
		delete alterNormal;
		alterNormal = 0;
	}

	return;
}

void Line::SetDirection(float xDirection, float yDirection) {
	// clean up to make sure memory is freed if needed
	CleanUpDir();
	dir = new Vector2D(xDirection, yDirection);
	// normalize the vector, to make sure future calculations will be accurate
	dir->Normalize();

	return;
}

void Line::SetDirection(const Vector2D& direction) {
	SetDirection(direction.x, direction.y);
	return;
}

void Line::CalculateNormals() {
	// clean up to make sure memory is freed if needed
	CleanUpNormal();
	normal = new Vector2D(-GetDirectionY(), GetDirectionX());
	// clean up to make sure memory is freed if needed
	CleanUpAlterNormal();
	alterNormal = new Vector2D(GetDirectionY(), -GetDirectionX());

	return;
}

void Line::GetCollisionPoint(const Circ* const c, Vector2D& pOut) const {
	// get the vector that points from the center of the circle to the line's origin
	Vector2D approxDir = Vector2D(GetX() - c->GetX(), GetY() - c->GetY());

	// then check normals
	Vector2D norm;
	GetNormal(norm);

	if (approxDir.Dot(norm) < 0) {
		// the normal is pointing away from the approximate direction it should be pointing towards
		// as a result, the alternate normal needs to be used
		GetAlternateNormal(norm);
	}

	// otherwise, they are oriented within 90 degrees of eachother, so pointing toward the same side

	// go on with the collision point
	pOut.x = c->GetX() + norm.x * c->GetRadius();
	pOut.y = c->GetY() + norm.y * c->GetRadius();

	return;
}

float Line::CalculateSlope() const {
	if (FloatEquals(GetDirectionX(), 0.f)) {
		// would divide by 0, so technically the slope would be infinite
		return 0.f;
	}

	// a = dy / dx
	return GetDirectionY() / GetDirectionX();
}

float Line::CalculateIntercept() const {
	// intercept of the line, so b in (y = ax + b)
	// b = y - ax (a being the slope)
	return (GetY() - CalculateSlope() * GetX());
}

float Line::CalculateDiscriminant(float a, float b, float c) const {
	// discriminant of a quadratic equation in the form of (ax^2 + bx + c = 0), so (b^2 - 4ac)
	return b * b - 4 * a * c;
}

bool Line::IsVerticalLine() const {
	// the direction in x has to be 0, however the direction in y can't be 0
	return FloatEquals(GetDirectionX(), 0.f) && !FloatEquals(GetDirectionY(), 0.f);
}
bool Line::IsHorizontalLine() const {
	// the direction in y has to be 0, however the direction in x can't be 0
	return FloatEquals(GetDirectionY(), 0.f) && !FloatEquals(GetDirectionX(), 0.f);
}
bool Line::IsNullLine() const {
	// the direction or distance should never be 0
	return (FloatEquals(GetDirectionX(), 0.f) && FloatEquals(GetDirectionY(), 0.f)) || FloatEquals(dist, 0.f) ;
}

bool Line::Contains(float x, float y) const {
	if (!IsNullLine()) {
		// check if the point is on the infinite line
		bool isOnLine;

		if (IsVerticalLine()) {
			// for a vertical line
			// as long as the x value respects the x value of the line, it will be on the infinite line
			isOnLine = x == GetX();
		} else {
			// if it is on the line, the parameters should follow the equation (y = ax + b)
			// where a is the slope and b is the intercept
			isOnLine = y == CalculateSlope() * x + CalculateIntercept();
		}

		// check if the given point is on the line
		if (dist < 0) {
			// the line is infinite, so there is no need to check if the point is within the scope of the line
			return isOnLine;
		} else {
			// the line is finite, so if the point is on the line, we must also check if it is in the scope of the line
			if (isOnLine) {
				// since the point is on the line, proceed to check if it is within the scope of the line
				float xMax, xMin, yMax, yMin;

				// end of the segment in x
				float xEnd = GetX() + GetDirectionX() * dist;
				// set scope in x
				if (GetX() > xEnd) {
					xMax = GetX();
					xMin = xEnd;
				} else {
					xMax = xEnd;
					xMin = GetX();
				}

				// end of the segment in y
				float yEnd = GetY() + GetDirectionY() * dist;
				// set scope in y
				if (GetY() > yEnd) {
					yMax = GetY();
					yMin = yEnd;
				} else {
					yMax = yEnd;
					yMin = GetY();
				}

				// check if x and y are within the scope of the segment
				if (x >= xMin && x <= xMax && y >= yMin && y <= yMax) {
					// we have to check the y values as well, in case it is an intersection of horizontal or vertical line
					return true;
				} else {
					// it is not within the scope
					return false;
				}
			} else {
				// it is not on the line, so do not bother checking the scope
				return false;
			}
			
		}
	} else {
		// the line is null
		return false;
	}
}

bool Line::Contains(const Vector2D& p) const {
	return Contains(p.x, p.y);
}

bool Line::CollidesWith(const Line* const l, Vector2D& hitOut) const {
	// get the variables for each line
	float a = CalculateSlope();
	float aL = l->CalculateSlope();
	float b = CalculateIntercept();
	float bL = l->CalculateIntercept();

	// parallel treatment
	if (FloatEquals(a, aL)) {
		// possibly parallel
		if (FloatEquals(a, 0.f)) {
			// but possibly not, in the case the slopes are 0 (could be vertical or horizontal line)
			if ((IsVerticalLine() && l->IsVerticalLine()) || (IsHorizontalLine() && l->IsHorizontalLine())) {
				// they are both in the same axis so they are actually parallel
				if (!l->IsNullLine() && !IsNullLine()) {
					// the lines are not null (have a length and direction)
					if (l->GetLength() > 0.f && GetLength() > 0.f) {
						// the line has a finite length
						if (Contains(l->GetX(), l->GetY()) || Contains(l->GetX() + l->GetDirectionX() * l->GetLength(), l->GetY() + l->GetDirectionY() * l->GetLength())) {
							// check both the origin and the end point of the line
							// if any of them are within this line, they touch

							// set the hit location
							hitOut.x = l->GetX();
							hitOut.y = l->GetY();

							return true;
						} else {
							return false;
						}
					} else {
						// at least one of the lines has an infinite length
						if (l->Contains(GetX(), GetY()) || Contains(l->GetX(), l->GetY())) {
							// since they are parallel and one is infinite, if the infinite contains any point of the other line, they are overlapping parallels

							// set the hit location
							hitOut.x = l->GetX();
							hitOut.y = l->GetY();

							return true;
						} else {
							return false;
						}
					}
				} else {
					// the line is null
					return false;
				}
			}
			
			// otherwise (else), they are not on the same axis, so not parallel, but their slopes are equal at 0
			// this means they are perpendicular, so skip to the next section (no return) for treatment

		} else {
			// they truly are parallel, and not vertical or horizontal lines
			if (FloatEquals(b, bL)) {
				// since the intercept is the same, they are overlapping

				// set the hit location
				hitOut.x = l->GetX();
				hitOut.y = l->GetY();

				return true;
			} else {
				// the intercept is different, so they are not overlapping
				return false;
			}
		}
	}

	// if they make it this far, it means no return was called
	// as such, they are not parallel and WILL intersect, if projected to infinity
	// at this point, there is one possible case where a == aL
	// when a == aL, a division by 0 would occur, so this must be treated
	// that is, if they are perpendicular vertical and horizontal lines
	if (FloatEquals(a, aL)) {
		if (IsVerticalLine()) {
			// this line is vertical, so l is horizontal
			// any x point in this line will work
			hitOut.x = GetX();
			// any y point in l will work
			hitOut.y = l->GetY();
		} else {
			// this line is horizontal, so l is vertical
			// any x point in l will work
			hitOut.x = l->GetX();
			// any y point in this Line will work
			hitOut.y = GetY();
		}
	} else {
		if (IsVerticalLine()) {
			// special treatment for vertical lines
			// any x point in this line will work
			hitOut.x = GetX();
			// y = ax + b (on l, as it is not vertical)
			hitOut.y = aL * hitOut.x + bL;
		} else {
			// this LineSegment isn't vertical
			if (l->IsVerticalLine()) {
				// special treatment for verticla lines
				// any x point in l will work
				hitOut.x = l->GetX();
			} else {
				// regular check
				// equalize the two equations to find the y value where they intersect
				// a x + b = aL xL + bL (where x == xL)
				hitOut.x = (bL - b) / (a - aL); // x = (bL - b) / (a - aL)
			}

			// as this line is not vertical, we can use the regular equation
			// to find the intersection point between the two lines at hitOut.x
			hitOut.y = a * hitOut.x + b; // y = a x + b
		}
	}

	// finally, if both lines contain the point, they intersect (collide)
	return Contains(hitOut) && l->Contains(hitOut);
}

bool Line::CollidesWith(const Rect* const r, Vector2D& hitOut) const {
	// create a line segment for each of the sides of the rectangle
	Transform* dummyPos = new Transform();
	Vector2D dummyDir;
	Line* bottom = new Line(dummyPos, dummyDir);
	r->GetBottomLine(*bottom);
	Line* left = new Line(dummyPos, dummyDir);
	r->GetLeftLine(*left);
	Line* top = new Line(dummyPos, dummyDir);
	r->GetTopLine(*top);
	Line* right = new Line(dummyPos, dummyDir);
	r->GetRightLine(*right);

	// check collisions, no collision by default
	bool isColliding = false;

	// if any of the sides intersect with the circle, it means there is a collision
	if (bottom->CollidesWith(this, hitOut)) {
		isColliding = true;
	} else if (right->CollidesWith(this, hitOut)) {
		isColliding = true;
	} else if (top->CollidesWith(this, hitOut)) {
		isColliding = true;
	} else if (left->CollidesWith(this, hitOut)) {
		isColliding = true;
	}

	// clean up all the pointers
	delete top, left, bottom, right;
	delete dummyPos;

	return isColliding;
}

bool Line::CollidesWith(const Circ* const c, Vector2D& hitOut) const {
	// for the line, a = slope, b = intercept
	// for the circle, c = x coordinate, d = y coordinate, r = radius
	float a = CalculateSlope();
	float b = CalculateIntercept();
	float cx = c->GetX();
	float cy = c->GetY();
	float r = c->GetRadius();

	// special treatment
	if (IsVerticalLine()) {
		// the line is vertical
		// distance between the starting point of the line and the center of the circle
		Vector2D lineToCircle;
		lineToCircle.x = c->GetX() - GetX();
		lineToCircle.y = c->GetY() - GetY();
		
		// distance between the line and the direction
		float dist = lineToCircle.Length();

		if (dist <= c->GetRadius()) {
			// the line is within the radius of the circle
			// y = d +- SqRt(r^2 - (x - c)^2)
			hitOut.x = GetX();
			hitOut.y = cy + sqrt(c->GetRadiusSq() - (hitOut.x - cx) * (hitOut.x - cx));
			if (Contains(hitOut.x, hitOut.y)) {
				// the collision point is within the line's scope

				// set the hit location
				GetCollisionPoint(c, hitOut);

				return true;
			} else {
				// the collision point isn't within the line's scope
				// so proceed to check the second point
				// y = d +- SqRt(r^2 - (x - c)^2)
				hitOut.x = GetX();
				hitOut.y = cy - sqrt(c->GetRadiusSq() - (hitOut.x - cx) * (hitOut.x - cx));
				if (Contains(hitOut.x, hitOut.y)) {
					// the collision point is within the line's scope

					// set the hit location
					GetCollisionPoint(c, hitOut);

					return true;
				} else {
					// the collision point isn't within the line's scope
					return false;
				}
			}
		} else { // the line is farther away in distance than the radius of the circle, and will never touch it
			return false;
		}
	}

	// otherwise, resume normal treatment
	// (a^2 + 1) * x^2 + 2(ab - ad - c) * x + (d^2 - r^2 + c^2 - 2bd + b^2) = 0
	// put this in the quadratic form of Ax^2 + Bx + C = 0
	float aDiscr = a * a + 1;
	float bDiscr = 2 * (a * b - a * cy - cx);
	float cDiscr = cy * cy - r * r + cx * cx - 2 * b * cy + b * b;
	// calculate the discriminant, B^2 - 4AC
	float discriminant = CalculateDiscriminant(aDiscr, bDiscr, cDiscr);
	if (discriminant < 0) {
		// the discriminant is below 0, it will never touch the circle
		return false;
	} else {
		// the line will intersect the circle at some point
		if (discriminant == 0) {
			// tangent intersection
			// x = -B / 2A, tangent point

			// set the hit location
			hitOut.x = -bDiscr / (2 * aDiscr);
			hitOut.y = a * hitOut.y + b;
			
			if (Contains(hitOut.x, hitOut.y)) {
				// the collision point is within the line's scope
				return true;
			} else {
				// the collision point isn't within the line's scope
				return false;
			}
		} else {
			// intersects in 2 points with the circle
			// x = ( -B +- SqRt(discriminant) ) / 2A, two points
			hitOut.x = (-bDiscr + sqrt(discriminant)) / (2 * aDiscr);
			hitOut.y = a * hitOut.x + b;

			if (Contains(hitOut.x, hitOut.y)) {
				// the collision point is within the line's scope

				// set the hit location
				GetCollisionPoint(c, hitOut);

				return true;
			} else {
				// the collision point is within the line's scope
				// so proceed to check the second point
				// x = ( -B +- SqRt(discriminant) ) / 2A, two points
				hitOut.x = (-bDiscr - sqrt(discriminant)) / (2 * aDiscr);
				hitOut.y = a * hitOut.x + b;

				if (Contains(hitOut.x, hitOut.y)) {
					// the collision point is within the line's scope

					// set the hit location
					GetCollisionPoint(c, hitOut);

					return true;
				} else {
					// the collision point isn't within the line's scope
					return false;
				}
			}
		}
	}
}

bool Line::CollidesWith(const Poly* const p, Vector2D& hitOut) const {
	// not implemented
	return false;
}

void Line::Draw() const {
	// not implemented
	return;
}

void Line::COut() const {
	std::cout << "Line: (" << GetX() << ", " << GetY() << ") ---> (" << GetDirectionX() << ", " << GetDirectionY() << ") ---> Distance " << GetDistance() << std::endl;
	return;
}